package com.ja.rfid.component;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Kisen
 * @email liuqs@jaid.cn
 * @date 2022/11/23 9:59
 * @detail RFID socket server端
 */
@Slf4j
@Component
public class SocketServer {

    //监听端口
    @Value("#{'${ja.rfid.listen.read.port}'.split(',')}")
    private String[] readPort;

    @Autowired
    private Executor executor;

    public void start() throws IOException {
        log.info("[线程{}]启动，端口：{}", Thread.currentThread().getName(), readPort);
        for (String port : readPort) {
            //ServerSocket监听端口
            ServerSocket server = new ServerSocket(Integer.valueOf(port));

            //异步任务调度处理
            CompletableFuture.runAsync(() -> {
                while (true) {
                    try {
                        log.info("[线程{}]socket监听端口{}...", Thread.currentThread().getName(), port);
                        //server尝试接收其他Socket的连接请求，server的accept方法是阻塞式的
                        Socket socket = server.accept();
                        log.info("[线程{}]socket已监听到端口{}，开始处理客户端数据", Thread.currentThread().getName(), port);

                        //每接收到一个Socket就建立一个线程来处理它
                        executor.execute(() -> {
                            try {
                                //接收客户端数据，并发送websocket客户端（前端）
                                receiveSocketData(socket, port);
                                socket.close();
                                log.info("[线程{}]socket端口{}关闭", Thread.currentThread().getName(), port);
                            } catch (IOException e) {
                                log.error("[线程{}]socket处理客户端{}数据异常：{}", Thread.currentThread().getName(), port, e);
                                throw new RuntimeException(e);
                            }
                        });

                    } catch (Exception e) {
                        log.error("[线程{}]socket监听或处理客户端{}数据异常：{}", Thread.currentThread().getName(), port, e);
                        try {
                            //休眠100ms再循环监听socket端口
                            TimeUnit.SECONDS.sleep(100);
                        } catch (InterruptedException ex) {
                            throw new RuntimeException(ex);
                        }
                    }
                }

            }, executor);
        }
    }

    /**
     * 给连接指定端口的客户端发送websocket数据（前端展示）
     *
     * @param port
     * @param data
     */
    public void sendWebSocketData(String port, Set<String> data) {
        if (CollectionUtils.isEmpty(data)) {
            return;
        }
        ConcurrentHashMap<String, WebSocketServer> webSocketMap = WebSocketServer.webSocketMap;
        if (webSocketMap != null && webSocketMap.size() > 0) {
            log.info("[线程{}]socket端口{}发送数据：{}", Thread.currentThread().getName(), port, data);
            for (Map.Entry<String, WebSocketServer> entry : webSocketMap.entrySet()) {
                String clientId = entry.getKey();
                WebSocketServer webSocketServer = entry.getValue();
                //给指定机器的所有客户端发送数据
                if (port.equals(StringUtils.substringBefore(clientId, "@"))) {
                    webSocketServer.sendMessage(JSON.toJSONString(data));
                }
            }
        }
    }

    /**
     * 接收指定端口的RFID客户端数据，并发送websocket数据
     *
     * @param socket
     * @param port
     * @throws IOException
     */
    public void receiveSocketData(Socket socket, String port) throws IOException {
        //使用字节流，防止不能换行输出
        BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
        int len;
        byte[] bytes = new byte[1024];
        while ((len = in.read(bytes)) != -1) {
            Set<String> data = byteToString(bytes, 0, len);
            log.info("[线程{}]socket端口{}接收到数据：{}", Thread.currentThread().getName(), port, data);

            //发送websocket数据
            sendWebSocketData(port, data);
        }
        in.close();

    }

    /**
     * 读取RFID数据
     *
     * @param bytes
     * @param offset
     * @param length
     * @return
     */
    public Set<String> byteToString(byte bytes[], int offset, int length) {
        int size = (length - offset) / 18;
        if (size < 1) {
            return Collections.emptySet();
        }

        Set<String> dataSet = new HashSet<>();
        StringBuffer sb = new StringBuffer();
        for (int i = offset; i < length; i++) {
            //1个byte等于2位十六进制，一个标签共36位十六进制，即为18位byte，故多标签查询：每18位byte截取一个标签
            //例：11 00 EE 00 00 00 00 00 00 00 00 00 00 00 01 80 1E 63
            String temp = Integer.toHexString(bytes[i] & 0xff);
            if (temp.length() == 1) {
                temp = "0" + temp;
            }
            sb.append(temp);
            //拼接完一个完整标签数据
            if ((i - offset) % 18 == 17) {
                dataSet.add(sb.toString());
                sb = new StringBuffer();
            }
        }
        if (StringUtils.isNotEmpty(sb.toString())) {
            dataSet.add(sb.toString());
        }

        if (CollectionUtils.isEmpty(dataSet)) {
            return Collections.emptySet();
        }
        //截取EPC数据
        dataSet = dataSet.stream().map(item -> {
            String data = item.toUpperCase();
            //log.info("[线程{}]socket读到原始数据：{}", Thread.currentThread().getName(), data);
            if (StringUtils.isNotEmpty(data) && data.length() >= 12) {
                //截取数据部分并替换前置的0
                data = data.substring(8, data.length() - 4).replaceAll("^(0+)", "");
                return data;
            }

            return StringUtils.EMPTY;
        }).filter(t -> StringUtils.isNotEmpty(t)).collect(Collectors.toSet());

        return dataSet;
    }

}
